local runtimeModule   = require('abtesting.adapter.runtime')
local redisModule     = require('abtesting.utils.redis')
local cache           = require('abtesting.utils.cache')
local systemConf      = require('abtesting.utils.init')
local handler	      = require('abtesting.error.handler').handler
local ERRORINFO	      = require('abtesting.error.errcode').info
local cjson           = require('cjson.safe')
local utils           = require('abtesting.utils.utils')
local semaphore       = require('abtesting.utils.sema')
local logmod	      = require('abtesting.utils.log')

local dolog           = utils.dolog
local doerror         = utils.doerror
local isNULL          = utils.isNULL

local prefixConf      = systemConf.prefixConf
local redisConf	      = systemConf.redisConf
local fields          = systemConf.fields
local runtimeLib      = prefixConf.runtimeInfoPrefix
local redirectInfo    = 'proxypass to http://'

local sema            = semaphore.sema
local upsSema         = semaphore.upsSema

local doredirect = function(info)
    local ok  = ERRORINFO.SUCCESS
    local err = redirectInfo..ngx.var.backend
    return dolog(ok, err, info)
end

local getHost = function()
    local hostkey = ngx.var.domain_name
    if hostkey then hostkey = string.lower(hostkey) end
    return hostkey

--    local host = ngx.req.get_headers()['Host']
--    local hostkey = string.match(host, "-%w+")
--    hostkey = string.lower(string.sub(hostkey, 2))
--    return hostkey
end

local hostname = getHost()

if isNULL(hostname) then
    local desc = 'hostname not set in location (rediskey前缀为空)'
    local errlog = dolog(ERRORINFO.ARG_BLANK_ERROR, '', desc)
    ngx.log(ngx.ERR, errlog)
    return nil
end

local getRewriteInfo = function()
    return redirectInfo..hostname
end

local red = redisModule:new(redisConf)
local log = logmod:new(hostname)

local connectdb = function(red, redisConf)
    if not red then
        red = redisModule:new(redisConf)
    end

    local ok, err = red:connectdb()
    if not ok then
        local info = ERRORINFO.REDIS_CONNECT_ERROR
        local errlog = dolog(info, err)
        log:errlog(errlog)
        return false, err
    end

    return ok, red
end

local setKeepalive = function(red)
    local ok, err = red:keepalivedb()  
    if not ok then
        local info = ERRORINFO.REDIS_KEEPALIVE_ERROR
        local errlog = dolog(info, err)
        log:errlog(errlog)
        return
    end
end

local getRuntime = function(database, hostname)
    local runtimeMod = runtimeModule:new(database, runtimeLib)
    return runtimeMod:get(hostname)
end

local getUserInfo = function(runtime)
    local userInfoModname = runtime[fields.userInfoModulename]
    local userInfoMod = require(userInfoModname)
    local userInfo = userInfoMod:get()
    return userInfo
end

local getUpstream = function(runtime, database, userInfo)
    local divModname = runtime[fields.divModulename]
    local policyKey  = runtime[fields.divDataKey]
    local divMod     = require(divModname)
    local divModule  = divMod:new(database, policyKey)
    local upstream   = divModule:getUpstream(userInfo)
    return upstream
end

-- fetch runtime info
local pfunc = function()
    local runtimeCache = cache:new(ngx.var.sysConfig)

    --step 1: read from cache
    local switch = runtimeCache:getSwtich(hostname)
    log:debug('1st fetch runtimeInfo in cache: ', 'switch=' .. (switch or 'nil'))

    if not switch then
        -- do nothing
    elseif switch < 1  then
        local info = 'switch < 1, div switch OFF'
        log:debug(info)
        return false, info
    else
        local ok, runtimeInfo = runtimeCache:getRuntime(hostname)
        if ok then
            return true, switch, runtimeInfo
        end
    end

    --step 2: acquire the lock
    local sem, err = sema:wait(0.01)
    if not sem then
        -- lock failed acquired
        -- but go on. This action just sets a fence
    end

    -- setp 3: read from cache again
    local switch = runtimeCache:getSwtich(hostname)
    log:debug('2nd fetch runtimeInfo in cache: ', 'switch=' .. (switch or 'nil'))

    if switch and switch < 1  then
        local info = 'switch < 1, div switch OFF'
        log:debug(info)
        return false, info
    end

    if switch and switch == 1 then
        local ok, runtimeInfo = runtimeCache:getRuntime(hostname)
        if ok then
            if sem then sema:post(1) end
            return true, switch, runtimeInfo
        end
    end

    -- step 4: fetch from redis
    local ok, db = connectdb(red, redisConf)
    if not ok then
        if sem then sema:post(1) end
        return ok, db
    end

    local runtimeInfo = getRuntime(db.redis, hostname)

    -- setp 5 set to sysConfig cache
    runtimeCache:setRuntime(hostname, runtimeInfo)
    switch = runtimeCache:getSwtich(hostname)
    log:debug('3rd fetch runtimeInfo in cache: ', 'switch=' .. (switch or 'nil'))

    -- step 6 release recourse
    if red then setKeepalive(red) end
    if sem then sema:post(1) end

    return true, switch, runtimeInfo
end

local ok, status, switch, runtimeInfo = xpcall(pfunc, handler)
if not ok then
    log:errlog("fetch runtimeInfo error:\n")
    return doerror(status, getRewriteInfo())
else
    if not status or not switch or switch < 1 then
        local info = 'no runtimeInfo in redis, switch set OFF. '
        local errlog = doredirect(info)
        log:debug(errlog, 'switch=' .. (switch or 'nil'))
        return
    else
        log:debug('fetch runtimeInfo: runtimeinfo=', cjson.encode(runtimeInfo))
    end
end

local runtime = runtimeInfo

-- fetch upstream
local pfunc = function()
    local userinfo = getUserInfo(runtime)
    log:debug('fetch userinfo: ', 'userinfo=' .. (userinfo or 'nil'))

    if not userinfo then
        return nil, 'the reqquest has no userinfo'
    end

    --step 1: read frome cache
    local upstreamCache = cache:new(ngx.var.kv_upstream)
    local upstream = upstreamCache:getUpstream(userinfo)
    log:debug('1st fetch upstream in cache: ', 'upstream=', (upstream or 'nil'))

    if not upstream then
        -- do nothing
    elseif upstream == -1 then
        local info = "1st fetch upstream: no upstream in cache, proxypass to default upstream"
        return nil, info
    else
        local info = '1st fetch upstream in cache: ' .. 'userinfo=' .. userinfo .. ', upstream=' .. upstream
        return upstream, info
    end

    --step 2: acquire the lock
    local sem, err = upsSema:wait(0.01)
    if not sem then
        -- lock failed acquired
        -- but go on. This action just sets a fence
    end

     --setp 3: read from cache again
    local upstream = upstreamCache:getUpstream(userinfo)
    log:debug('2nd fetch upstream in cache: ', 'upstream=', (upstream or 'nil'))

    if not upstream then
        -- do nothing
    elseif upstream == -1 then
        local info = '2nd fetch upstream no upstream in cache, proxypass to default upstream'
        return nil, info
    else
        local info = '2nd fetch upstream in cache: ' .. 'userinfo=' .. userinfo .. ', upstream=' .. upstream
        return upstream, info
    end

    -- step 4: fetch from redis
    local ok, db = connectdb(red, redisConf)
    if not ok then
        if sem then upsSema:post(1) end
        return nil, db
    end

    if userinfo then
        local upstream = getUpstream(runtime, db.redis, userinfo)

        if not upstream then
            upstreamCache:setUpstream(userinfo, -1)
            local info = '3rd fetch upstream in redis: ' .. 'upstream is nil' .. ', userinfo=' .. userinfo
            log:debug(info)
        else
            if sem then upsSema:post(1) end
            if red then setKeepalive(red) end

            upstreamCache:setUpstream(userinfo, upstream)
            local info = '3rd fetch upstream in redis: ' .. 'upstream='.. upstream .. ', userinfo=' .. userinfo
            log:debug(info)
            return upstream, info
        end
    end

    if sem then upsSema:post(1) end
    if red then setKeepalive(red) end

    return nil, 'the reqquest has no target upstream'
end


local _upstream
local status, info, desc = xpcall(pfunc, handler)
if not status then
    doerror(info)
else
    _upstream = info
end

if _upstream then
    ngx.var.backend = _upstream
end

local info = doredirect(desc)
log:debug(info)
